/* picoc's interface to the underlying platform. most platform-specific code
 * is in platform/platform_XX.c and platform/library_XX.c */

#include "table.h"
#include "cstdlib/cstdlib.h"
#include "clibrary.h"
#include "platform.h"

void Platform::PrintSourceTextErrorLine(IOFILE *stream, const char *fileName,
    const char *sourceText, int line, int characterPos)
{
    int LineCount;
    int CCount;
    const char *LinePos;
    const char *CPos;

    if (sourceText != NULL) {
        /* find the source line */
        for (LinePos = sourceText, LineCount = 1; *LinePos != '\0' &&
                LineCount < line; LinePos++) {
            if (*LinePos == '\n')
                LineCount++;
        }

        /* display the line */
        for (CPos = LinePos; *CPos != '\n' && *CPos != '\0'; CPos++)
            Cstdlib::PrintCh(*CPos, stream);
        Cstdlib::PrintCh('\n', stream);

        /* display the error position */
        for (CPos = LinePos, CCount = 0; *CPos != '\n' && *CPos != '\0' &&
                (CCount < characterPos || *CPos == ' '); CPos++, CCount++) {
            if (*CPos == '\t')
                Cstdlib::PrintCh('\t', stream);
            else
                Cstdlib::PrintCh(' ', stream);
        }
    } else {
        /* assume we're in interactive mode - try to make the arrow match
            up with the input text */
        for (CCount = 0;
                CCount < characterPos+(int)strlen(INTERACTIVE_PROMPT_STATEMENT);
                CCount++)
            Cstdlib::PrintCh(' ', stream);
    }
    PlatformPrintf(stream, "^\n%s:%d:%d ", fileName, line, characterPos);
}

/* exit with a message */
void Platform::ProgramFail(ParseState *parser, const char *message, ...)
{
    va_list Args;

    PrintSourceTextErrorLine(parser->pc->cStdOut, parser->fileName,
        parser->sourceText, parser->line, parser->characterPos);
    va_start(Args, message);
    PlatformVPrintf(parser->pc->cStdOut, message, Args);
    va_end(Args);
    PlatformPrintf(parser->pc->cStdOut, "\n");
    PlatformExit(parser->pc, 1);
}

/* exit with a message, when we're not parsing a program */
void Platform::ProgramFailNoParser(Interpreter *pc, const char *Message, ...)
{
    va_list Args;

    va_start(Args, Message);
    PlatformVPrintf(pc->cStdOut, Message, Args);
    va_end(Args);
    PlatformPrintf(pc->cStdOut, "\n");
    PlatformExit(pc, 1);
}

/* like ProgramFail() but gives descriptive error messages for assignment */
void Platform::AssignFail(ParseState *parser, const char *format,
    ValueType *Type1, ValueType *Type2, int Num1, int Num2,
    const char *funcName, int ParamNo)
{
    IOFILE *stream = parser->pc->cStdOut;

    PrintSourceTextErrorLine(parser->pc->cStdOut, parser->fileName,
        parser->sourceText, parser->line, parser->characterPos);
    PlatformPrintf(stream, "can't %s ", (funcName == NULL) ? "assign" : "set");

    if (Type1 != NULL)
        PlatformPrintf(stream, format, Type1, Type2);
    else
        PlatformPrintf(stream, format, Num1, Num2);

    if (funcName != NULL)
        PlatformPrintf(stream, " in argument %d of call to %s()", ParamNo,
            funcName);

    PlatformPrintf(stream, "\n");
    PlatformExit(parser->pc, 1);
}

/* exit lexing with a message */
void Platform::LexFail(Interpreter *pc, struct LexState *Lexer, const char *Message, ...)
{
    va_list Args;

    PrintSourceTextErrorLine(pc->cStdOut, Lexer->fileName, Lexer->sourceText,
        Lexer->line, Lexer->characterPos);
    va_start(Args, Message);
    PlatformVPrintf(pc->cStdOut, Message, Args);
    va_end(Args);
    PlatformPrintf(pc->cStdOut, "\n");
    PlatformExit(pc, 1);
}

/* printf for compiler error reporting */
void Platform::PlatformPrintf(IOFILE *stream, const char *format, ...)
{
    va_list Args;

    va_start(Args, format);
    PlatformVPrintf(stream, format, Args);
    va_end(Args);
}

void Platform::PlatformVPrintf(IOFILE *stream, const char *format, va_list Args)
{
    const char *FPos;

    for (FPos = format; *FPos != '\0'; FPos++) {
        if (*FPos == '%') {
            FPos++;
            switch (*FPos) {
            case 's':
                Cstdlib::PrintStr(va_arg(Args, char*), stream);
                break;
            case 'd':
                Cstdlib::PrintSimpleInt(va_arg(Args, int), stream);
                break;
            case 'c':
                Cstdlib::PrintCh(va_arg(Args, int), stream);
                break;
            case 't':
                Clibrary::PrintType(va_arg(Args, ValueType*), stream);
                break;
            case 'f':
                Cstdlib::PrintFP(va_arg(Args, double), stream);
                break;
            case '%':
                Cstdlib::PrintCh('%', stream);
                break;
            case '\0':
                FPos--;
                break;
            default:
                break;
            }
        } else
            Cstdlib::PrintCh(*FPos, stream);
    }
}

/* make a new temporary name. takes a static buffer of char [7] as a parameter.
 * should be initialized to "XX0000"
 * where XX can be any characters */
char *Platform::PlatformMakeTempName(Interpreter *pc, char *TempNameBuffer)
{
    int CPos = 5;

    while (CPos > 1) {
        if (TempNameBuffer[CPos] < '9') {
            TempNameBuffer[CPos]++;
            return MyTable::TableStrRegister(pc, TempNameBuffer);
        } else {
            TempNameBuffer[CPos] = '0';
            CPos--;
        }
    }

    return MyTable::TableStrRegister(pc, TempNameBuffer);
}

#ifdef DEBUGGER
static int gEnableDebugger = true;
#else
static int gEnableDebugger = false;
#endif

/* mark where to end the program for platforms which require this */
jmp_buf picocExitBuf;

void Platform::PlatformInit(Interpreter *pc) {}

void Platform::PlatformCleanup(Interpreter *pc) {}

/* get a line of interactive input */
char *Platform::PlatformGetLine(char *Buf, int MaxLen, const char *Prompt) {
    if (Prompt != NULL)
        printf("%s", Prompt);

    fflush(stdout);
    return fgets(Buf, MaxLen, stdin);
}

/* get a character of interactive input */
int Platform::PlatformGetCharacter() {
    fflush(stdout);
    return getchar();
}

/* write a character to the console */
void Platform::PlatformPutc(unsigned char OutCh, union OutputStreamInfo *stream) {
    putchar(OutCh);
}


/* exit the program */
void Platform::PlatformExit(Interpreter *pc, int RetVal) {
    pc->picocExitValue = RetVal;
    longjmp(pc->picocExitBuf, 1);
}

void Platform::MsvcSetupFunc(Interpreter *pc) {}

void Platform::CTest(ParseState *parser, Value *returnValue,
           Value **param, int numArgs) {
    printf("test(%d)\n", param[0]->val->integer);
    param[0]->val->integer = 1234;
}

void Platform::CLineNo(ParseState *parser, Value *returnValue,
             Value **param, int numArgs) {
    returnValue->val->integer = parser->line;
}

/* list of all library functions and their prototypes */
struct LibraryFunction MsvcFunctions[] =
{
    {Platform::CTest, "void Test(int);"},
    {Platform::CLineNo, "int LineNo();"},
    {NULL, NULL}
};

void Platform::PlatformLibraryInit(Interpreter *pc) {
    pc->IncludeRegister("picoc_msvc.h", &MsvcSetupFunc, &MsvcFunctions[0], NULL);
}